package com.example.auth.authorization.config;


import com.example.auth.authorization.constants.ParameterNamesEmail;
import com.example.auth.authorization.constants.ParameterNamesPassword;
import com.example.auth.authorization.constants.ParameterNamesSms;
import com.example.auth.authorization.dao.FormUserDetailsAuthenticationProvider;
import com.example.auth.authorization.email.EmailAuthenticationConverter;
import com.example.auth.authorization.email.EmailAuthenticationProvider;
import com.example.auth.authorization.entrypoint.LoginRedirectEntryPoint;
import com.example.auth.authorization.password.PasswordAuthenticationConverter;
import com.example.auth.authorization.password.PasswordAuthenticationProvider;
import com.example.auth.authorization.sms.SmsAuthenticationConverter;
import com.example.auth.authorization.sms.SmsAuthenticationProvider;
import com.example.auth.config.BasicAuthProperties;
import com.example.auth.constant.SecurityConstants;
import com.example.auth.handler.*;
import com.example.auth.util.SecurityUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.security.web.util.matcher.RequestMatcher;

import java.util.List;



@Slf4j
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(jsr250Enabled = true, securedEnabled = true)
public class AuthSecurityBeanConfig {
    @Resource
    RedisTemplate redisTemplate;


    @Resource
    UserDetailsService userDetailsService;
    @Resource
    BasicAuthProperties authProperties;

    private static final String CUSTOM_CONSENT_REDIRECT_URI = "/oauth2/consent/redirect";
    @Resource
    PasswordEncoder passwordEncoder;

    @Resource
    FormUserDetailsAuthenticationProvider formUserDetailsAuthenticationProvider;

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http,
                                                                      AuthenticationManager authenticationManager,
                                                                      OAuth2AuthorizationService authorizationService,
                                                                      OAuth2TokenGenerator<?> tokenGenerator) throws Exception {

        OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer();


        http
                // 当未登录时访问认证端点时重定向至login页面
                .exceptionHandling((exceptions) -> exceptions
                        .accessDeniedHandler(SecurityUtils::exceptionHandler)
                        .authenticationEntryPoint(new LoginRedirectEntryPoint(authProperties.LOGIN_URL))
                );

//        http.oauth2ResourceServer((resourceServer) -> resourceServer
//                .jwt(Customizer.withDefaults())
//                .accessDeniedHandler(SecurityUtils::exceptionHandler)
//                .authenticationEntryPoint(SecurityUtils::exceptionHandler)
//        );


        //配置Oidc
        authorizationServerConfigurer
                .oidc(Customizer.withDefaults())
                .tokenRevocationEndpoint(Customizer.withDefaults())
                // 让认证服务器元数据中有自定义的认证方式
                .authorizationServerMetadataEndpoint(metadata ->
                        metadata.authorizationServerMetadataCustomizer(customizer -> {
                            customizer.grantTypes(x ->
                                    List.of(
                                            ParameterNamesSms.GRANT_TYPE,
                                            ParameterNamesEmail.GRANT_TYPE,
                                            ParameterNamesPassword.GRANT_TYPE
                                    ));
                        }))
                // 设置自定义用户确认授权页
                .authorizationEndpoint(authorizationEndpoint -> {
                    // 校验授权确认页面是否为完整路径；是否是前后端分离的页面
                    boolean absoluteUrl = UrlUtils.isAbsoluteUrl(authProperties.CONSENT_PAGE_URI);
                    // 如果是分离页面则重定向，否则转发请求
                    authorizationEndpoint.consentPage(absoluteUrl ? CUSTOM_CONSENT_REDIRECT_URI : authProperties.CONSENT_PAGE_URI);
                    // 适配前后端分离的授权确认页面，成功/失败响应json
                    authorizationEndpoint.errorResponseHandler(new ConsentFailureHandler());
                    authorizationEndpoint.authorizationResponseHandler(new ConsentSuccessHandler());
//                    authorizationEndpoint
//                            .authorizationRequestConverters(converters ->
//                            converters.addAll(List.of(
//                                    new PasswordAuthenticationConverter(),
//                                    new SmsAuthenticationConverter(),
//                                    new EmailAuthenticationConverter()
//                            )))
//                            .authenticationProviders(providers ->
//                                    providers.addAll(List.of(
//                                            new PasswordAuthenticationProvider(tokenGenerator, authenticationManager, authorizationService),
//                                            new SmsAuthenticationProvider(tokenGenerator, authenticationManager, authorizationService, redisTemplate),
//                                            new EmailAuthenticationProvider(tokenGenerator, authenticationManager, authorizationService, redisTemplate)
//                    )));
                })
                .tokenEndpoint(tokenEndpoint -> tokenEndpoint
                        .accessTokenRequestConverters(converters ->
                                converters.addAll(List.of(
                                        new PasswordAuthenticationConverter(),
                                        new SmsAuthenticationConverter(),
                                        new EmailAuthenticationConverter()
                                )))
                        .authenticationProviders(providers ->
                                providers.addAll(List.of(
                                        new PasswordAuthenticationProvider(tokenGenerator, authenticationManager, authorizationService),
                                        new SmsAuthenticationProvider(tokenGenerator, authenticationManager, authorizationService, redisTemplate),
                                        new EmailAuthenticationProvider(tokenGenerator, authenticationManager, authorizationService, redisTemplate)
                                )))
                        .accessTokenResponseHandler(new LoginSuccessHandler()) // 自定义成功响应
                        .errorResponseHandler(new LoginFailureHandler())


                );
        RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
        http.securityMatcher(endpointsMatcher)
                .authorizeHttpRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())
                .csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
                .apply(authorizationServerConfigurer);
        return http.build();
    }



    @Bean
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
            throws Exception {

        // 禁用 csrf 与 cors
        http

                .csrf(AbstractHttpConfigurer::disable)
                .cors(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests((authorize) -> authorize                        // 放行静态资源和不需要认证的url
                        .requestMatchers("/oauth2/consent/parameters", "/login","/sms/**").permitAll()
                        .anyRequest().authenticated())
                .userDetailsService(userDetailsService)
                //.sessionManagement(managementConfigurer -> managementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .formLogin(formLogin ->
                        formLogin
                                .loginPage(SecurityConstants.LOGIN_PATH)
                                // 登录成功和失败改为写回json，不重定向了
                                .successHandler(new LoginSuccessHandler())
                                .failureHandler(new LoginFailureHandler())

                )


                .authenticationProvider(formUserDetailsAuthenticationProvider)
                .logout(formLogout -> {
                    formLogout
                            .logoutUrl(SecurityConstants.LOGOUT_PATH)
                            .logoutSuccessHandler(new LogoutHandler());
                    //.invalidateHttpSession(true)
                    //.deleteCookies(JSESSIONID);
                });

        // 添加BearerTokenAuthenticationFilter，将认证服务当做一个资源服务，解析请求头中的token
        http.oauth2ResourceServer((resourceServer) -> resourceServer
                .jwt(Customizer.withDefaults())
                .accessDeniedHandler(SecurityUtils::exceptionHandler)
                .authenticationEntryPoint(SecurityUtils::exceptionHandler)
        );


        return http.build();
    }




}




